//
//  ScanImagePage.swift
//  ReportScan
//
//  Created by woniu on 2025/7/22.
//自动扫描图片中错误信息，并导出excel表格标注错误报告图片地址
//从指定csv文件中，下载每一张报告图片，识别并判断是否有问题，并标注出来，导出excel表格。

import SwiftUI
import SDWebImageSwiftUI
import Vision
import Combine

struct ScanImagePage: View {
    @State var itemList:[ExcelItem] = []
    @State var totalCount = 0
    
    @State var leftScanItem:ExcelItem?
    @State var leftScanIndex:Int?
    
    @State var rightScanItem:ExcelItem?
    @State var rightScanIndex:Int?
    
    @State private var isCanGenderExcel = false
    @State var isStop = false
    private let pubNumberFormatter = NumberFormatter()
    @State var leftErrorCount = 0
    @State var rightErrorCount = 0
    
    @State var leftScanedCount = 0
    @State var rightScanedCount = 0
    @State private var timer: AnyCancellable?
    @State var counter = 0
    @State var exceptionItems:[ExcelItem] = []
    
    @State var onSelectedItemUrl:String?
    /**
     匹配报告里面，医生签名信息为英文的
     送檢医生: dszyp
     送检时间:2025-03-19
     检验医生:dszyp
     */
    private let kDoctorNamePattern = "医生: [a-zA-Z0-9]"
    private let kDoctorNamePattern1 = "医生:[a-zA-Z0-9]"
    private let kDoctorNamePattern2 = "医生： [a-zA-Z0-9]"
    private let kDoctorNamePattern3 = "医生：[a-zA-Z0-9]"
    /**
     匹配报告图片里面，用户基本信息没有的
     性别:未知
     |年龄: 0岁
     */
    private let kUserNameNullPattern = "性别:未知"
    private let kUserNameNullPattern2 = "性别: 未知"
    
    /**
     报告标题有问题，标题只有一个“尿”字，正常应该是"xxx尿检报告单"
     */
    private let kReportTitlePattern = "报告单"
    private let kReportTitlePattern2 = "振告单"
    private let kReportTitlePattern3 = "根告单"
    private let kReportTitlePattern4 = "撮告单"
    
    
    var body: some View {
        Text("从指定csv文件中，下载每一张报告图片，识别并判断是否有问题，并标注出来，导出excel表格。")
        VStack(alignment: .leading) {
            HStack(alignment: .top){
                
                Button(role: .none) {
                    self.onPickCSVFile()
                } label: {
                    Text("选择CSV报告")
                }
                Text("共\(totalCount)条数据")
                Button(role: .none) {
                    print("开始扫描")
                    startTimer()
                    startScan()
                } label: {
                    Text("开始扫描")
                }
                Text("耗时：\(counter)s ，已扫描:\(leftScanedCount + rightScanedCount)条，剩下:\(totalCount-leftScanedCount-rightScanedCount)条")
                Text("Error:\(leftErrorCount+rightErrorCount)条")
                Button(role: .none) {
                    isStop = true
                    stopTimer()
                } label: {
                    Text("暂停")
                }
                Button() {
                    generNewExcel()
                } label: {
                    Text("导出Excel")
                }
                
                
            }.frame(height: 60)
            Divider()
            HStack(){
                
                VStack(){
                    Text("异常报告图片")
                    List(exceptionItems, id: \.self) { item in
                        WebImage(url: URL(string:item.url))
                            .resizable()
                            .scaledToFit()
                            .frame(width: 250, height: 150)
                            .border(Color.gray, width: 1)
                            .padding()
                            .overlay( TextField("", text: .constant("第\(item.indexInCsv)条/\(ScanErrorEnum.getErrorString(error: item.errorList)):\(item.url)"))
                                .disabled(false)
                                .textFieldStyle(PlainTextFieldStyle()), alignment: .top).onTapGesture {
                                    self.onSelectedItemUrl = item.url
                                }
                    }
                }.frame(width: 300)
                
                if let url = self.onSelectedItemUrl,!url.isEmpty{
                    WebImage(url: URL(string:url)).scaledToFit()
                }else{
                    Text("没有图片")
                }
                
            }
        }
        .padding()
        Spacer()
    }
    
    //启动线程1下载图片
    private func downloadLeftImage() {
        self.leftScanItem?.disScan = true
        
        if let url = self.leftScanItem?.url,let imageURL = URL(string:url){
            URLSession.shared.dataTask(with: imageURL) { data, response, error in
                
                if let data = data, let downloadedImage = NSImage(data: data),let cgImg = downloadedImage.cgImage {
                    performOCR(on:cgImg) { re, content in
                        
                        DispatchQueue.main.async {
                            let errorList = findResultBy(content: content)
                            if !errorList.isEmpty{
                                self.leftScanItem?.errorList.append(contentsOf: errorList)
                                self.leftErrorCount += 1
                                self.exceptionItems.append(self.leftScanItem!)
                            }
                            //下一张
                            
                            self.onScanNextItem()
                        }
                        
                    }
                    
                } else if let error = error {
                    print("下载图片出错: \(self.leftScanItem?.url ?? "")\(error.localizedDescription)")
                    self.leftScanItem?.errorList.append(.errorDownloadFail)
                    self.leftErrorCount += 1
                    self.exceptionItems.append(self.leftScanItem!)
                    //下一张
                    self.onScanNextItem()
                }
            }.resume()
        }
        
    }
    
    //启动线程2下载图片
    private func downloadRightImage() {
        self.rightScanItem?.disScan = true
        
        if let url = self.rightScanItem?.url,let imageURL = URL(string:url){
            URLSession.shared.dataTask(with: imageURL) { data, response, error in
                
                if let data = data, let downloadedImage = NSImage(data: data),let cgImg = downloadedImage.cgImage {
                    performOCR(on:cgImg) { re, content in
                        DispatchQueue.main.async {
                            let errorList = findResultBy(content: content)
                            if !errorList.isEmpty{
                                self.rightScanItem?.errorList.append(contentsOf: errorList)
                                self.rightErrorCount += 1
                                self.exceptionItems.append(self.rightScanItem!)
                            }
                            //上一张
                            self.onScanPreItem()
                        }
                        
                    }
                    
                } else if let error = error {
                    print("下载图片出错: \(self.rightScanItem?.url ?? "")\(error.localizedDescription)")
                    
                    self.rightScanItem?.errorList.append(.errorDownloadFail)
                    self.rightErrorCount += 1
                    self.exceptionItems.append(self.rightScanItem!)
                    //上一张
                    self.onScanPreItem()
                }
            }.resume()
        }
        
    }
    
    
    private func startTimer() {
        timer = Timer.publish(every: 1, on: .main, in: .common)
            .autoconnect()
            .sink { _ in
                counter += 1
                
            }
    }
    
    private func stopTimer() {
        timer?.cancel()
    }
    
    //根据正则匹配内容
    private func findResultBy(content:String?)->[ScanErrorEnum]{
        guard let content = content else{return []}
        var errors:[ScanErrorEnum] = []
        //用户基本信息是否为空
        let userName = matchString(with: kUserNameNullPattern, in: content)
        let userName2 = matchString(with: kUserNameNullPattern2, in: content)
        //通过性别判断
        if !userName.isEmpty || !userName2.isEmpty{
            errors.append(.errorBaseInfo)
            print("发现错误：\(ScanErrorEnum.errorBaseInfo.rawValue)")
            //通过年龄判断
        }else if let age = matchString(with: "\\d+岁", in: content).first{
            let intAge = age.replacingOccurrences(of: "岁", with: "")
            let intValue = pubNumberFormatter.number(from: intAge)?.intValue ?? 0
            if intValue <= 0{
                errors.append(.errorBaseInfo)
            }
        }
        
        //医生签名是否显示的是英文
        let signError = matchString(with: kDoctorNamePattern, in: content)
        let signError2 = matchString(with: kDoctorNamePattern1, in: content)
        let signError3 = matchString(with: kDoctorNamePattern2, in: content)
        let signError4 = matchString(with: kDoctorNamePattern3, in: content)
        if !signError.isEmpty || !signError2.isEmpty || !signError3.isEmpty || !signError4.isEmpty{
            errors.append(.errorSignDoctor)
            print("发现错误：\(ScanErrorEnum.errorSignDoctor.rawValue)")
        }
        
        //        //下面四种，一个都没发现
        //        let titleError = content.qmui_stringMatched(byPattern:self.kReportTitlePattern) ?? ""
        //        let titleError2 = content.qmui_stringMatched(byPattern:self.kReportTitlePattern2) ?? ""
        //        let titleError3 = content.qmui_stringMatched(byPattern:self.kReportTitlePattern3) ?? ""
        //        let titleError4 = content.qmui_stringMatched(byPattern:self.kReportTitlePattern4) ?? ""
        //
        //        if titleError.isEmpty && titleError2.isEmpty && titleError3.isEmpty && titleError4.isEmpty{
        //            errors.append(.errorReprotTitle)
        //            LogUtil.print(info: "发现错误：\(ScanErrorEnum.errorReprotTitle.rawValue)")
        //        }
        
        return errors
    }
    
    //正则匹配内容
    func matchString(with pattern: String, in input: String) -> [String] {
        do {
            let regex = try NSRegularExpression(pattern: pattern)
            let matches = regex.matches(in: input, range: NSRange(input.startIndex..., in: input))
            return matches.map {
                String(input[Range($0.range, in: input)!])
            }
        } catch {
            print("创建正则表达式时出错: \(error)")
            return []
        }
    }
    
    //开始扫描
    private func startScan(){
        if totalCount <= 0{return}
        self.exceptionItems.removeAll()
        self.onSelectedItemUrl = nil
        leftScanIndex = 0
        counter = 0
        //启动左边扫描（列表前面往后面扫描）
        if let index = leftScanIndex{
            leftScanedCount = 1
            leftScanItem = itemList[index]
            print("正向扫描:\(leftScanItem!.url)")
            downloadLeftImage()
        }
        //数量大于5，才启动同时扫描（列表后面往前面扫描）
        if totalCount > 5{
            rightScanIndex = totalCount - 1
            if let index = rightScanIndex{
                rightScanedCount = 1
                rightScanItem = itemList[index]
                downloadRightImage()
                print("反向扫描:\(rightScanItem!.url)")
            }
        }
    }
    
    //下一张
    private func onScanNextItem(){
        if isStop == true{return}
        
        if let index = leftScanIndex{
            if index + 1 < totalCount{
                let newIndex = index + 1
                if itemList[newIndex].disScan == false{
                    self.leftScanedCount += 1
                    print("下一张")
                    leftScanIndex = newIndex
                    leftScanItem = itemList[newIndex]
                    print("下载图片:\(leftScanItem!.url)")
                    downloadLeftImage()
                }else{
                    onScanFinish()
                }
                
            }else{
                onScanFinish()
            }
        }
        
        
    }
    //上一张
    private func onScanPreItem(){
        if isStop == true{return}
        
        if let index = rightScanIndex{
            if index - 1 >= 0{
                let newIndex = index - 1
                if itemList[newIndex].disScan == false{
                    self.rightScanedCount += 1
                    print("上一张")
                    rightScanIndex = newIndex
                    rightScanItem = itemList[newIndex]
                    print("下载图片:\(rightScanItem!.url)")
                    downloadRightImage()
                }else{
                    print("扫描结束")
                    onScanFinish()
                }
                
            }else{
                print("扫描结束")
                onScanFinish()
            }
        }
    }
    
    //结束扫描
    private func onScanFinish(){
        print("扫描结束")
        stopTimer()
    }
    
    //生成新excel文件
    private func generNewExcel(){
        var csvData:[[String]] = []
        for (i,model) in itemList.enumerated(){
            if i == 0{
                if let index = model.scanResultIndex{
                    model.data[index] = "扫描结果"
                }
                csvData.append(model.data)
            }else{
                var errorStr = ""
                if model.disScan == true{
                    let error = model.errorList.map { type in
                        return type.rawValue
                    }.joined(separator: "/")
                    errorStr.append("\(error)")
                }else{
                    errorStr = "未扫描"
                }
                if let index = model.scanResultIndex{
                    model.data[index] = errorStr
                }
                csvData.append(model.data)
            }
        }
        if csvData.isEmpty{
            print("没有数据可以生成")
            return
        }
        
        let openPanel = NSOpenPanel()
        openPanel.title = "选择存放目录"
        openPanel.showsHiddenFiles = false
        openPanel.allowsMultipleSelection = false
        openPanel.canChooseDirectories = true
        openPanel.canCreateDirectories = true
        openPanel.allowedContentTypes = [.folder]
        openPanel.begin {result in
            if result == .OK, let url = openPanel.url {
                let formatter = DateFormatter()
                formatter.dateFormat = "yyyy_MM_dd_HH:mm_ss"
                let fileName = "\(formatter.string(from: Date()))_ScanResult.csv"
                let newUrl = url.appending(component: fileName)
                print("存储到：\(newUrl)")
                writeCSVData(csvData, toPath: newUrl)
            }
        }
    }
    
    //导入csv文件
    private func onPickCSVFile(){
        let openPanel = NSOpenPanel()
        openPanel.title = "选择CSV文件"
        openPanel.showsHiddenFiles = false
        openPanel.allowsMultipleSelection = false
        openPanel.canChooseDirectories = false
        openPanel.canCreateDirectories = false
        
        openPanel.begin {result in
            if result == .OK, let url = openPanel.url {
                
                self.itemList = genderModels(fileUrl: url)
                self.totalCount = self.itemList.count
                print("总共:\(self.totalCount)条数据")
            }
        }
    }
    
    //读取文件，生成模型
    private func genderModels(fileUrl:URL)->[ExcelItem]{
        do {
            let csvString = try String(contentsOf: fileUrl, encoding: .utf8)
            let lines = csvString.components(separatedBy: .newlines)
            var models:[ExcelItem] = []
            //报告地址在csv中的位置，从0开始
            var urlIndex:Int?
            //扫描结果存放位置
            var scanResultIndex:Int?
            
            for (index,line) in lines.enumerated() {
                var columns = line.components(separatedBy: ",")
                let name = columns.first ?? ""
                if !name.isEmpty{
                    //标题
                    if index == 0{
                        columns.append("扫描结果")
                        scanResultIndex = columns.count - 1
                    }else{
                        columns.append("")
                    }
                    if urlIndex == nil{
                        for(i,subItem) in columns.enumerated(){
                            if subItem.hasPrefix("http"){
                                urlIndex = i
                            }
                        }
                    }
                    let item  = ExcelItem()
                    item.data = columns
                    if let urlIndex = urlIndex{
                        item.url = columns[urlIndex]
                    }
                    if let scanResultIndex = scanResultIndex{
                        item.scanResultIndex = scanResultIndex
                    }
                    models.append(item)
                }
            }
            for(index,item) in models.enumerated(){
                item.indexInCsv = index
            }
            return models
            
        } catch {
            print("读取文件时出错: \(error)")
            return []
        }
    }
    
    // 将修改后的数据写回到 CSV 文件
    func writeCSVData(_ data: [[String]], toPath path: URL) {
        var csvString = ""
        for row in data {
            let line = row.joined(separator: ",")
            csvString.append(line + "\n")
        }
        do {
            try csvString.write(toFile: path.path(), atomically: true, encoding: .utf8)
            print("文件已成功写入。")
        } catch {
            print("写入文件时出错: \(error)")
        }
    }
    
    //扫描图片
    private func performOCR(on cgImage: CGImage,finishBlock: ((_ re:Bool,_ content:String?)->())?) {
        let request = VNRecognizeTextRequest {request, error in
            if let error = error {
                print("Error performing OCR: \(error)")
                finishBlock?(false,nil)
                return
            }
            
            guard let observations = request.results as? [VNRecognizedTextObservation] else {
                finishBlock?(false,nil)
                return
            }
            
            var recognizedText = ""
            for observation in observations {
                guard let topCandidate = observation.topCandidates(1).first else {
                    continue
                }
                recognizedText += topCandidate.string + "\n"
            }
            DispatchQueue.main.async {
                print("扫描到内容->\(recognizedText)")
                finishBlock?(true,recognizedText)
            }
        }
        request.recognitionLevel = .accurate
        request.recognitionLanguages = ["zh-Hans","en-US"]
        let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
        do {
            try handler.perform([request])
        } catch {
            print("Error performing OCR: \(error)")
            finishBlock?(false,nil)
        }
    }
}

#Preview {
    ScanImagePage()
}
